#define  _WIN32_WINNT   0x0500

//-----------------------------------------------------------------------------
// Include files
//-----------------------------------------------------------------------------
#include <windows.h>
#include <process.h>
#include <ddraw.h>
#include <stdio.h>
#include <stdarg.h>

#include "predator.h"
#include "MTVERIFY.h"
#include "..\pcommon\BitmapUtil.h"


//-----------------------------------------------------------------------------
// Local definitions
//-----------------------------------------------------------------------------
#define NAME                "Predator"
#define TITLE               "Please wait..."
#define SAFE_RELEASE(t, p)	do { if(p) { t##_Release((p)); (p)=NULL; } } while(0)

//-----------------------------------------------------------------------------
// Global data
//-----------------------------------------------------------------------------
LPDIRECTDRAW7               g_pDD = NULL;
LPDIRECTDRAWSURFACE7		g_pddsFrontBuffer = NULL;
LPDIRECTDRAWSURFACE7		g_pddsBackBuffer = NULL;
LPDIRECTDRAWSURFACE7		g_pddsBitmap = NULL;
RECT						g_rcScreenRect = { 0, 0, 0, 0 };
LONG						g_dwRenderWidth = 0, g_dwRenderHeight = 0;
static char					g_szBitmap[] = "..\\Data\\progress.bmp";
static unsigned				WM_USER_CLOSE_WAIT;

//-----------------------------------------------------------------------------
// Name: ReleaseAllObjects()
// Desc: Finished with all objects we use; release them
//-----------------------------------------------------------------------------
void ReleaseAllObjects(void)
{
    if (g_pDD != NULL)
    {
        SAFE_RELEASE(IDirectDrawSurface7, g_pddsFrontBuffer);
		SAFE_RELEASE(IDirectDrawSurface7, g_pddsBackBuffer);
		SAFE_RELEASE(IDirectDrawSurface7, g_pddsBitmap);
		
        IDirectDraw_Release(g_pDD);
        g_pDD = NULL;
    }
}

//-----------------------------------------------------------------------------
// Name: InitFail()
// Desc: This function is called if an initialization function fails
//-----------------------------------------------------------------------------
HRESULT InitFail(HWND hWnd, HRESULT hRet, LPCTSTR szError,...)
{
    char                        szBuff[128];
    va_list                     vl;

    va_start(vl, szError);
    vsprintf(szBuff, szError, vl);
    ReleaseAllObjects();
    MessageBox(hWnd, szBuff, TITLE, MB_OK);
    DestroyWindow(hWnd);
    va_end(vl);
    return hRet;
}

/* flip the back buffer to be front */
HRESULT Flip(void)
{
    // We are in windowed mode, so perform a blit.
    return IDirectDrawSurface7_Blt( g_pddsFrontBuffer, &g_rcScreenRect,
									g_pddsBackBuffer, NULL, DDBLT_WAIT,
									NULL );
}

//-----------------------------------------------------------------------------
// Name: RestoreAll()
// Desc: Restore all lost objects
//-----------------------------------------------------------------------------
HRESULT RestoreAll(void)
{
    HRESULT hRet;

    hRet = IDirectDrawSurface7_Restore(g_pddsFrontBuffer);
    if (hRet == DD_OK)
    {
        hRet = IDirectDrawSurface7_Restore(g_pddsBitmap);
        if (hRet == DD_OK)
        {
            DDReLoadBitmap(g_pddsBitmap, g_szBitmap);
        }
    }
    return hRet;
}

/* determine if the flag should be increased or decreased */
void Change_Current(int* flag, pboolean increased)
{
	int val = *flag;

	if (true == increased)
	{
		*flag = ++val;
	}
	else if (false == increased)
	{
		*flag = --val;
	}
}

//-----------------------------------------------------------------------------
// Name: UpdateFrame()
// Desc: Displays the proper text for the page
//-----------------------------------------------------------------------------
void UpdateFrame()
{
    static int                  currentFrame = 0;
	static pboolean				increased = true;
    RECT                        rcSrcRect, rcDestRect;
    HRESULT                     hRet;
	DDBLTFX                     ddbltfx;

	// Use the blter to do a color fill to clear the back buffer
    ZeroMemory(&ddbltfx, sizeof(ddbltfx));
    ddbltfx.dwSize = sizeof(ddbltfx);
    ddbltfx.dwFillColor = 0;
    IDirectDrawSurface7_Blt(g_pddsBackBuffer, NULL, NULL, NULL,
							DDBLT_COLORFILL | DDBLT_WAIT, &ddbltfx);

    // Decide which frame will be blitted next
	// Move to next frame;
	if (currentFrame > 10 || currentFrame < 0)
		increased ^= increased;
	Change_Current(&currentFrame, increased);
	
    // Blit the stuff for the next frame
    rcSrcRect.left = 0;
    rcSrcRect.top = currentFrame * 47;
    rcSrcRect.right = 280;
    rcSrcRect.bottom = currentFrame * 47 + 47;

	rcDestRect.left = (g_dwRenderWidth - 280) / 2;
	rcDestRect.top = (g_dwRenderHeight - 47) / 2;
	rcDestRect.right = (g_dwRenderWidth + 280) / 2;
	rcDestRect.bottom = (g_dwRenderHeight + 47) / 2;

    while (TRUE)
    {
        hRet = IDirectDrawSurface7_Blt(g_pddsBackBuffer, &rcDestRect,
									   g_pddsBitmap, &rcSrcRect,
									   DDBLT_WAIT, NULL);
        if (hRet == DD_OK)
            break;
        if (hRet == DDERR_SURFACELOST)
        {
            hRet = RestoreAll();
            if (hRet != DD_OK)
                return;
        }
        if (hRet != DDERR_WASSTILLDRAWING)
            return;
    }

    // Flip the surfaces
    while (TRUE)
    {
        hRet = Flip();
        if (hRet == DD_OK)
            break;
        if (hRet == DDERR_SURFACELOST)
        {
            hRet = RestoreAll();
            if (hRet != DD_OK)
                break;
        }
        if (hRet != DDERR_WASSTILLDRAWING)
            break;
    }
}

//-----------------------------------------------------------------------------
// Name: WindowProc()
// Desc: The Main Window Procedure
//-----------------------------------------------------------------------------
long FAR PASCAL
WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
        case WM_DESTROY:
            // Clean up and quit the thread
            ReleaseAllObjects();
			PostQuitMessage(0);
            return 0;

        case WM_SETCURSOR:
            // Turn off the cursor since this is a full-screen app
            SetCursor(NULL);
            return 0;
    }
    return DefWindowProc(hWnd, message, wParam, lParam);
}

//-----------------------------------------------------------------------------
// Name: CreateWindowedBuffers(HWND)
// Desc: Creates the primary and (optional) backbuffer for rendering.
//       Windowed mode and fullscreen mode are handled differently.
//-----------------------------------------------------------------------------
HRESULT CreateWindowedBuffers(HWND hWnd)
{
    HRESULT hr;
	DDSURFACEDESC2 ddsd;
	LPDIRECTDRAWCLIPPER pcClipper;

    // Get the dimensions of the viewport and screen bounds
    GetClientRect( hWnd, &g_rcScreenRect );
    ClientToScreen( hWnd, (POINT*)&g_rcScreenRect.left );
    ClientToScreen( hWnd, (POINT*)&g_rcScreenRect.right );
    g_dwRenderWidth  = g_rcScreenRect.right  - g_rcScreenRect.left;
    g_dwRenderHeight = g_rcScreenRect.bottom - g_rcScreenRect.top;

    // Create the primary surface
    ZeroMemory( &ddsd, sizeof(ddsd) );
    ddsd.dwSize         = sizeof(ddsd);
    ddsd.dwFlags        = DDSD_CAPS;
    ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;

    if( FAILED( hr = IDirectDraw_CreateSurface( g_pDD, &ddsd, &g_pddsFrontBuffer, NULL ) ) )
    {
        fprintf( stderr, "Error: Can't create primary surface.\n" );
        if( hr != DDERR_OUTOFVIDEOMEMORY )
            return E_FAIL;
        fprintf( stderr, "Error: Out of video memory.\n" );
        return E_FAIL;
    }

    // If in windowed-mode, create a clipper object
    if( FAILED( hr = IDirectDraw_CreateClipper( g_pDD, 0, &pcClipper, NULL ) ) )
    {
        fprintf( stderr, "Error: Couldn't create clipper.\n" );
        return E_FAIL;
    }

    // Associate the clipper with the window
    IDirectDrawClipper_SetHWnd( pcClipper, 0, hWnd );
    IDirectDrawSurface7_SetClipper( g_pddsFrontBuffer, pcClipper );
    SAFE_RELEASE( IDirectDrawClipper, pcClipper );

    // Create a backbuffer
    ddsd.dwFlags        = DDSD_WIDTH | DDSD_HEIGHT | DDSD_CAPS;
    ddsd.dwWidth        = g_dwRenderWidth;
    ddsd.dwHeight       = g_dwRenderHeight;
    ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_3DDEVICE;

    if( FAILED( hr = IDirectDraw_CreateSurface( g_pDD, &ddsd, &g_pddsBackBuffer, NULL ) ) )
    {
        fprintf( stderr, "Error: Couldn't create the backbuffer.\n" );
        if( hr != DDERR_OUTOFVIDEOMEMORY )
            return E_FAIL;
        fprintf( stderr, "Error: Out of video memory.\n" );
        return E_FAIL;
    }

    return S_OK;
}

//-----------------------------------------------------------------------------
// Name: InitWait()
// Desc: Do work required for every instance of the application:
//          Create the window, initialize data
//-----------------------------------------------------------------------------
static HWND g_hWnd;

HRESULT InitWait(void)
{
    WNDCLASS                    wc;
    HRESULT                     hRet;

    // Set up and register window class
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = WindowProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = global_hInstance;
    wc.hIcon = NULL;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
    wc.lpszMenuName = NULL;
    wc.lpszClassName = NAME;
    RegisterClass(&wc);

    // Create a window
    g_hWnd = CreateWindow(NAME,
                          TITLE,
                          WS_POPUP,
                          0,
                          0,
                          GetSystemMetrics(SM_CXSCREEN),
                          GetSystemMetrics(SM_CYSCREEN),
                          NULL,
                          NULL,
                          global_hInstance,
                          NULL);
    if (!g_hWnd)
        return FALSE;
    ShowWindow(g_hWnd, SW_NORMAL);
    UpdateWindow(g_hWnd);

    ///////////////////////////////////////////////////////////////////////////
    // Create the main DirectDraw object
    ///////////////////////////////////////////////////////////////////////////
    hRet = DirectDrawCreateEx(NULL, (VOID**)&g_pDD, &IID_IDirectDraw7, NULL);
    if (hRet != DD_OK)
        return InitFail(g_hWnd, hRet, "DirectDrawCreateEx FAILED");

    // Get windowed mode
    hRet = IDirectDraw_SetCooperativeLevel(g_pDD, g_hWnd, DDSCL_NORMAL);
    if (hRet != DD_OK)
        return InitFail(g_hWnd, hRet, "SetCooperativeLevel FAILED");

	// Create the primary surface with 1 back buffer
    if (FAILED(hRet = CreateWindowedBuffers(g_hWnd)))
		return InitFail(g_hWnd, hRet, "CreateWindowedBuffers FAILED");

	// Create the offscreen surface, by loading our bitmap.
    g_pddsBitmap = DDLoadBitmap(g_pDD, g_szBitmap, 0, 0);
    if (g_pddsBitmap == NULL)
        return InitFail(g_hWnd, hRet, "DDLoadBitmap FAILED");

    return DD_OK;
}

DWORD WINAPI Sys_Init(void* datain)
{
	MSG							msg;
	DWORD                       dwLastTime;
    DWORD                       dwThisTime;

	(void)datain;

	/* register a user defined message to notify a closing event */
	WM_USER_CLOSE_WAIT = RegisterWindowMessage("Close the waitting window.");

	/* display the waiting window */
    if (DD_OK != InitWait())
        return FALSE;

    //-------------------------------------------------------------------------
    //                          The Message Pump
    //-------------------------------------------------------------------------
    dwLastTime = 0;
    dwThisTime = timeGetTime();
    
	while(TRUE)
	{
        while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            if (WM_QUIT == msg.message)
			{
				goto _quit;
			}
			else if (WM_USER_CLOSE_WAIT == msg.message)
			{
				DestroyWindow(g_hWnd);
			}
            else
			{
				// Translate and dispatch the message
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
        }
        
		dwThisTime = timeGetTime();
        // 1000/FPS = number of milliseconds to wait
        if ((dwThisTime - dwLastTime) >= (1000/10))
        {
            UpdateFrame();
            dwLastTime = timeGetTime();
        }
        else
		{
            // Make sure we go to sleep if we have nothing else to do
            Sleep(10);
		}
	}

_quit:
	return msg.wParam;
}

static HANDLE hThrdInit;
static DWORD idThrd;

void PreInit(void)
{
	MTVERIFY(hThrdInit = (HANDLE)_beginthreadex(	
								NULL,		/* security level */ 
								0,			/* stack size */
								Sys_Init,	/* starting address of thread procedure */
								NULL,		/* parameter list */
								0,			/* init flag */
								&idThrd));	/* thread handle */
	MTVERIFY(CloseHandle(hThrdInit));
}

void WaitForInit(void)
{
	if (0 == WM_USER_CLOSE_WAIT)
	{
		fprintf(stderr, "waitting window not rendered normally.\n");
		return;
	}
	PostThreadMessage(idThrd, WM_USER_CLOSE_WAIT, 0, 0);
}
